using System;
using System.Text;
using GeoFramework;

namespace GeoFramework.Gps.Nmea
{
	/// <summary>
	/// Represents a $GPGSA sentence describing fixed GPS satellites and precision
	/// information.
	/// </summary>
	/// <remarks>This sentence reveals whether or not the GPS device has a fix and
	/// also indicates information about how precise measurements currently are.  Dilution of
	/// precision values should be monitored frequently if an application makes decisions based
	/// on GPS data.  This is important because a GPS device can transmit grossly inaccurate
	/// coordinates which should be ignored.</remarks>
	public sealed class GpgsaSentence : NmeaSentence //, IDisposable 
	{
		#region Private Variables

		private FixMode pFixMode;
		private FixMethod pFixMethod;
		private SatelliteCollection pFixedSatellites;
		private DilutionOfPrecision pMeanDilutionOfPrecision;
		private DilutionOfPrecision pHorizontalDilutionOfPrecision;
		private DilutionOfPrecision pVerticalDilutionOfPrecision;

		#endregion

		#region Constructors

		public GpgsaSentence(string sentence) : base(sentence)
		{
			// Is the checksum valid?
			if (!IsValid && !IsPartialSentenceParsingAllowed)
			{
				pFixMode = FixMode.Manual;
				pFixMethod = FixMethod.NoFix;
				pFixedSatellites = SatelliteCollection.Empty;
				pMeanDilutionOfPrecision = DilutionOfPrecision.Maximum;
				pHorizontalDilutionOfPrecision = DilutionOfPrecision.Maximum;
				pVerticalDilutionOfPrecision = DilutionOfPrecision.Maximum;
				// No.  Do no parsing
				return;
			}

			// $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
			// Get fix selection method
			try
			{
				if(WordCount > 1)
				{
					pFixMode = Words[1] == "A" ? FixMode.Automatic : FixMode.Manual;
				}

				// Get the fix quality information
				if(WordCount > 2)
				{
					switch (Words[2])
					{
						case "1":
							pFixMethod = FixMethod.NoFix;
							break;
						case "2":
							pFixMethod = FixMethod.Fix2D;
							break;
						case "3":
							pFixMethod = FixMethod.Fix3D;
							break;
					}
				}
				else
				{
					pFixMethod = FixMethod.NoFix;
				}

				// Build an  As Byte() of satellites which currently have a fix
				pFixedSatellites = new SatelliteCollection();

				for(int index = 3; index < 15; index++)
				{
					// Are there any more words to process?  If not, exit
					if(WordCount <= index)
						break;
					// Is the word blank?  If not, process it
					if (Words[index].Length != 0)
						pFixedSatellites.Add(new Satellite(int.Parse(Words[index], NmeaCultureInfo)));
				}

				// Set overall dilution of precision
				if(WordCount > 15 && Words[15].Length != 0)
					pMeanDilutionOfPrecision = new DilutionOfPrecision(double.Parse(Words[15], NmeaCultureInfo));
				else
					pMeanDilutionOfPrecision = DilutionOfPrecision.Maximum;

				// Set horizontal dilution of precision
				if(WordCount > 16 && Words[16].Length != 0)
					pHorizontalDilutionOfPrecision = new DilutionOfPrecision(double.Parse(Words[16], NmeaCultureInfo));
				else
					pHorizontalDilutionOfPrecision = DilutionOfPrecision.Maximum;
				
				// Set vertical dilution of precision
				if(WordCount > 17 && Words[17].Length != 0)
					pVerticalDilutionOfPrecision = new DilutionOfPrecision(double.Parse(Words[17], NmeaCultureInfo));
				else
					pVerticalDilutionOfPrecision = DilutionOfPrecision.Maximum;
			}
			catch(Exception ex)
			{
				throw new NmeaSentenceException(this, "A $GPGSA sentence contained unrecognizable data.", ex);
			}
		}
	
		public GpgsaSentence(byte[] bytes) : this(ASCIIEncoding.ASCII.GetString(bytes, 0, bytes.Length))
		{
		}

		public GpgsaSentence(FixMode fixMode, FixMethod fixMethod,
			SatelliteCollection fixedSatellites, DilutionOfPrecision meanDilutionOfPrecision,
			DilutionOfPrecision horizontalDilutionOfPrecision, DilutionOfPrecision verticalDilutionOfPrecision)
		{
			pFixMode = fixMode;
			pFixMethod = fixMethod;
			pFixedSatellites = SatelliteCollection.ReadOnly(fixedSatellites);
			pMeanDilutionOfPrecision = meanDilutionOfPrecision;
			pHorizontalDilutionOfPrecision = horizontalDilutionOfPrecision;
			pVerticalDilutionOfPrecision = verticalDilutionOfPrecision;
			// Build a $GPGSA sentence
			StringBuilder Sentence = new StringBuilder();
			Sentence.Append("$GPGSA,");			
			// Write fix mode
			switch(FixMode)
			{
				case FixMode.Automatic:
					Sentence.Append("A");
					break;
				case FixMode.Manual:
					Sentence.Append("M");
					break;
			}
			Sentence.Append(",");
			// Write fix method
			switch(FixMethod)
			{
				case FixMethod.NoFix:
					Sentence.Append("1");
					break;
				case FixMethod.Fix2D:
					Sentence.Append("2");
					break;
				case FixMethod.Fix3D:
					Sentence.Append("3");
					break;
			}
			Sentence.Append(",");
			// Append # fixed satellites
			if(pFixedSatellites.Count == 0)
				Sentence.Append(",,,,,,,,,,,,");
			else
			{
				for(int count = 0; count <= 11; count++)
				{
					// If we have a fixed satellite, output its PRC
					if(count < FixedSatellites.Count)
						Sentence.Append(FixedSatellites[count].PseudorandomNumber.ToString().PadLeft(2, '0'));
					Sentence.Append(",");			
				}
			}
			// Append mean DOP
			Sentence.Append(MeanDilutionOfPrecision.Value.ToString("#.#", NmeaCultureInfo));
			Sentence.Append(",");
			// Append horizontal DOP
			Sentence.Append(HorizontalDilutionOfPrecision.Value.ToString("#.#", NmeaCultureInfo));
			Sentence.Append(",");
			// Append vertical DOP
			Sentence.Append(VerticalDilutionOfPrecision.Value.ToString("#.#", NmeaCultureInfo));
			// Set the sentence
			SetSentence(Sentence.ToString());
			SetChecksum();
		}


		#endregion
		
		#region Public Members

		/// <summary>Returns the fix mode currently in use.</summary>
		public FixMode FixMode
		{
			get
			{
				return pFixMode;
			}
		}

		/// <summary>Returns the fix method currently in use.</summary>
		public FixMethod FixMethod
		{
			get
			{
				return pFixMethod;
			}
		}

		/// <summary>
		/// Indicates whether a satellite fix is currently in progress.
		/// </summary>
		public bool IsFixObtained
		{
			get
			{
				// Update the fix flag
				return 
					pFixMethod == FixMethod.Fix2D 
					| pFixMethod == FixMethod.Fix3D;
			}
		}

		/// <summary>Returns a collection of satellites used to calculate the current location.</summary>
		public SatelliteCollection FixedSatellites
		{
			get
			{
				return pFixedSatellites;
			}
		}

		/// <summary>
		/// Returns the current level of precision as it relates to latitude and
		/// longitude.
		/// </summary>
		public DilutionOfPrecision HorizontalDilutionOfPrecision
		{
			get
			{
				return pHorizontalDilutionOfPrecision;
			}
		}

		/// <summary>
		/// Returns the current level of precision as it relates to latitude, longitude and
		/// altitude.
		/// </summary>
		public DilutionOfPrecision MeanDilutionOfPrecision
		{
			get
			{
				return pMeanDilutionOfPrecision;
			}
		}

		/// <summary>Returns the current level of precision as it relates to altitude.</summary>
		public DilutionOfPrecision VerticalDilutionOfPrecision
		{
			get
			{
				return pVerticalDilutionOfPrecision;
			}
		}

		/// <summary>
		/// Indicates that this sentence is supported by GPS.NET.
		/// </summary>
		public override bool IsSupported
		{
			get
			{
				return true;
			}
		}

		#endregion

		#region Static Members

		/// <summary>Represents a $GPGSA sentence with no value.</summary>
		public static readonly GpgsaSentence Empty = new GpgsaSentence(FixMode.Manual,
			FixMethod.NoFix, new SatelliteCollection(0), DilutionOfPrecision.Maximum, DilutionOfPrecision.Maximum, 
			DilutionOfPrecision.Maximum);

		/// <summary>Returns a new instance by interpreting the specified string.</summary>
		public static GpgsaSentence Parse(string sentence)
		{
			return new GpgsaSentence(sentence);
		}

		#endregion

}
}
